/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * System Includes:
 **/
#include "commonheaders.h"
#include "Reminder.h"
#include "dlgAnniversaryList.h"

#include "m_message.h"
#include "m_email.h"

namespace NAnniversaryList {

#define IsLeap(wYear)	(!(((wYear) % 4 != 0) || (((wYear) % 100 == 0) && ((wYear) % 400 != 0))))

class CAnnivList;

static CAnnivList *gpDlg = NULL;

/***********************************************************************************************************
 * class CAnnivList
 ***********************************************************************************************************/

class CAnnivList
{
	HWND	_hDlg;
	HWND	_hList;
	SIZE	_sizeMin;
	SHORT	_sortOrder;
	INT		_sortHeader;
	INT		_curSel;
	INT		_numRows;
	BYTE	_bRemindEnable;
	HANDLE	_mHookExit;

	typedef INT (CALLBACK* CMPPROC)(LPARAM, LPARAM  LPARAM);  

	enum EColumn {
		COLUMN_ETA = 0,
		COLUMN_CONTACT,
		COLUMN_PROTO,
		COLUMN_AGE,
		COLUMN_DESC,
		COLUMN_DATE,
	};

	enum EFilter {
		FILTER_ALL = 0,
		FILTER_BIRTHDAY,
		FILTER_ANNIV,
		FILTER_DISABLED_REMINDER
	};

	struct CFilter {
		WORD	wDaysBefore;
		BYTE	bFilterIndex;
		LPSTR	pszProto;
		LPTSTR	pszAnniv;

		CFilter() {
			wDaysBefore = (WORD)-1;
			bFilterIndex = 0;
			pszProto = NULL;
			pszAnniv = NULL;
		}
	} _filter;

	struct CItemData {
		HANDLE		_hContact;
		MAnnivDate*	_pDate;
		WORD		_wDaysBefore;
		BYTE		_wReminderState;

		CItemData(HANDLE hContact, MAnnivDate &date) {
			_hContact = hContact;
			_wReminderState = date.RemindOption();
			_wDaysBefore = date.RemindOffset();
			_pDate = new MAnnivDate(date);
		}

		~CItemData() {
			if(_pDate) {
				// save changes
				if(_wReminderState != _pDate->RemindOption() || _wDaysBefore != _pDate->RemindOffset()) {
					_pDate->RemindOffset(_wDaysBefore);
					_pDate->RemindOption(_wReminderState);
					_pDate->DBWriteReminderOpts(_hContact);
				}
				delete _pDate;
				_pDate = NULL;
			}
		}
	};

	/**
	 * name:	CAnchor
	 * class:	CAnnivList
	 * desc:	subclass to handle the movement of child controls on size change
	 **/
	class CAnchor {
	public:
		enum EAnchor {
			ANCHOR_LEFT = 1,
			ANCHOR_RIGHT = 2,
			ANCHOR_TOP = 4,
			ANCHOR_BOTTOM = 8,
			ANCHOR_ALL = ANCHOR_LEFT | ANCHOR_RIGHT | ANCHOR_TOP | ANCHOR_BOTTOM
		};

	private:
		WINDOWPOS*	_wndPos;
		HDWP		_hdWnds;
		RECT		_rcParent;
	
		VOID _ScreenToClient(HWND hWnd, LPRECT rc) {
			POINT pt = { rc->left, rc->top };
			ScreenToClient(hWnd, &pt);
			rc->right += pt.x - rc->left;
			rc->bottom += pt.y - rc->top;
			rc->left = pt.x;
			rc->top = pt.y;
		}

		VOID _MoveWindow(HWND hWnd, INT anchors) {
			if(!(_wndPos->flags & SWP_NOSIZE)) {
				RECT rcc = _CalcPos(hWnd, anchors);
				MoveWindow(hWnd, rcc.left, rcc.top, rcc.right - rcc.left, rcc.bottom - rcc.top, FALSE);
			}
		}

		RECT _CalcPos(HWND hWnd, INT anchors) {
			RECT rcc;

			GetWindowRect(hWnd, &rcc);
			_ScreenToClient(_wndPos->hwnd, &rcc);
			if(!(_wndPos->flags & SWP_NOSIZE)) {
				// calculate difference between new and old size
				const INT cx = _wndPos->cx - _rcParent.right + _rcParent.left;
				const INT cy = _wndPos->cy - _rcParent.bottom + _rcParent.top;

				if(cx != 0 || cy != 0) {
					// move client rect points to the desired new position
					if(!(anchors & ANCHOR_LEFT) || (anchors & ANCHOR_RIGHT))
						rcc.right += cx;
					if(!(anchors & ANCHOR_TOP) || (anchors & ANCHOR_BOTTOM))
						rcc.bottom += cy;
					if((anchors & ANCHOR_RIGHT) && (!(anchors & ANCHOR_LEFT)))
						rcc.left += cx;
					if((anchors & ANCHOR_BOTTOM) && (!(anchors & ANCHOR_TOP)))
						rcc.top += cy;
				}
			}
			return rcc;
		}

	public:
		CAnchor(WINDOWPOS* wndPos, SIZE minSize) {
			GetWindowRect(wndPos->hwnd, &_rcParent);
			if(wndPos->cx < minSize.cx) wndPos->cx = minSize.cx;
			if(wndPos->cy < minSize.cy) wndPos->cy = minSize.cy;
			_wndPos = wndPos;
			_hdWnds = BeginDeferWindowPos(2);
		}
		
		~CAnchor() {
			EndDeferWindowPos(_hdWnds);
		}
		
		VOID MoveCtrl(WORD idCtrl, INT anchors) {
			if(!(_wndPos->flags & SWP_NOSIZE)) {
				HWND hCtrl = GetDlgItem(_wndPos->hwnd, idCtrl);
				RECT rcc = _CalcPos(hCtrl, anchors);
				_hdWnds = DeferWindowPos(_hdWnds, hCtrl, HWND_NOTOPMOST, rcc.left, rcc.top, rcc.right - rcc.left, rcc.bottom - rcc.top, SWP_NOZORDER);
			}
		}
	};

	/**
	 * name:	cmpProc
	 * class:	CAnnivList
	 * desc:	compare function used by ListView_SortItemsEx to sort the listview
	 * param:	iItem1	- index of the first item
	 *			iItem2	- index of the second item
	 *			pDlg	- pointer to the class' object
	 * return:	a number indicating comparison result
	 **/
	static INT CALLBACK cmpProc(INT iItem1, INT iItem2, CAnnivList* pDlg)
	{
		if(pDlg) {
			TCHAR szText1[MAX_PATH];
			TCHAR szText2[MAX_PATH];

			switch(pDlg->_sortHeader) {
				case COLUMN_CONTACT:
				case COLUMN_PROTO:
				case COLUMN_DESC:
					ListView_GetItemText(pDlg->_hList, iItem1, pDlg->_sortHeader, szText1, MAX_PATH);
					ListView_GetItemText(pDlg->_hList, iItem2, pDlg->_sortHeader, szText2, MAX_PATH);
					return pDlg->_sortOrder * lstrcmp(szText1, szText2);
				case COLUMN_AGE:
				case COLUMN_ETA:
					ListView_GetItemText(pDlg->_hList, iItem1, pDlg->_sortHeader, szText1, MAX_PATH);
					ListView_GetItemText(pDlg->_hList, iItem2, pDlg->_sortHeader, szText2, MAX_PATH);
					return pDlg->_sortOrder * (_ttoi(szText1) - _ttoi(szText2));
				case COLUMN_DATE: {
					CItemData *id1 = pDlg->ItemData(iItem1), 
							  *id2 = pDlg->ItemData(iItem2);
					if(PtrIsValid(id1) && PtrIsValid(id2)) 
						return pDlg->_sortOrder * id1->_pDate->Compare(*id2->_pDate);
				}
			}
		}
		return 0;
	}

	/**
	 * name:	DlgProc
	 * class:	CAnnivList
	 * desc:	default constructor
	 * param:	hDlg	- handle of the dialog window
	 *			uMsg	- message to handle
	 *			wParam	- message dependend parameter
	 *			lParam	- message dependend parameter
	 * return:	depends on message
	 **/
	static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
		CAnnivList *pDlg = (CAnnivList*)GetUserData(hDlg);
		
		switch(uMsg) {
			case WM_INITDIALOG:
			{
				INT i = 0;
				HWND hCtrl;

				pDlg = (CAnnivList*)lParam;
				SetUserData(hDlg, lParam);
				pDlg->_hDlg = hDlg;

				// init pointer listview control
				pDlg->_hList = GetDlgItem(hDlg, EDIT_ANNIVERSARY_DATE);
				if(!pDlg->_hList) return FALSE;

				// set icons
				HICON hIcon = NIcoLib::GetIcon(ICO_DLG_ANNIVERSARY);
				if(hIcon == NIcoLib::hEmptyIcon) hIcon = NULL;
				SendDlgItemMessage(hDlg, ICO_DLGLOGO, STM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
				SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);

				// insert columns into the listboxes					
				ListView_SetExtendedListViewStyle(pDlg->_hList, LVS_EX_FULLROWSELECT);

				// add columns
				if(pDlg->AddColumn(CAnnivList::COLUMN_ETA, _T("ETA"), 40) ||
				   pDlg->AddColumn(CAnnivList::COLUMN_CONTACT, _T("Contact"), 160) ||
				   pDlg->AddColumn(CAnnivList::COLUMN_PROTO, _T("Proto"), 50) ||
				   pDlg->AddColumn(CAnnivList::COLUMN_AGE, _T("Age/Nr."), 40) ||
				   pDlg->AddColumn(CAnnivList::COLUMN_DESC, _T("Anniversary"), 100) ||
				   pDlg->AddColumn(CAnnivList::COLUMN_DATE, _T("Date"), 80)) 
				{
				  return FALSE;
				}

				TranslateDialogDefault(hDlg);
				
				// save minimal size
				RECT rc;
				GetWindowRect(hDlg, &rc);
				pDlg->_sizeMin.cx = rc.right - rc.left;
				pDlg->_sizeMin.cy = rc.bottom - rc.top;
				
				// restore position and size
				Utils_RestoreWindowPosition(hDlg, NULL, MODNAME, "AnnivDlg_");

				// add filter strings
				if(hCtrl = GetDlgItem(hDlg, COMBO_VIEW)) {
					ComboBox_AddString(hCtrl, TranslateT("All contacts"));
					ComboBox_AddString(hCtrl, TranslateT("Birthdays only"));
					ComboBox_AddString(hCtrl, TranslateT("Anniversaries only"));
					ComboBox_AddString(hCtrl, TranslateT("Disabled reminder"));
					ComboBox_SetCurSel(hCtrl, pDlg->_filter.bFilterIndex);
				}

				// init reminder groups
				pDlg->_bRemindEnable = DBGetByte(SET_REMIND_ENABLED, DEFVAL_REMIND_ENABLED) != NReminder::REMIND_OFF;
				if(hCtrl = GetDlgItem(hDlg, CHECK_REMIND)) {
					Button_SetCheck(hCtrl, pDlg->_bRemindEnable ? BST_INDETERMINATE : BST_UNCHECKED);
					EnableWindow(hCtrl, pDlg->_bRemindEnable);
				}

				CheckDlgButton(hDlg, CHECK_POPUP, DBGetByte(SET_ANNIVLIST_POPUP, FALSE));
				// set number of days to show contact in advance
				SetDlgItemInt(hDlg, EDIT_DAYS, pDlg->_filter.wDaysBefore , FALSE);
				if(hCtrl = GetDlgItem(hDlg, CHECK_DAYS)) {
					Button_SetCheck(hCtrl, DBGetByte(SET_ANNIVLIST_FILTER_DAYSENABLED, FALSE));
					DlgProc(hDlg, WM_COMMAND, MAKEWPARAM(CHECK_DAYS, BN_CLICKED), (LPARAM)hCtrl);
				}
				return TRUE;
			}

			case WM_CTLCOLORSTATIC:
				switch(GetDlgCtrlID((HWND)lParam)) {
					case STATIC_WHITERECT:
					case ICO_DLGLOGO:
					case TXT_NAME:
						SetBkColor((HDC)wParam, RGB(255, 255, 255));
						return (BOOL)GetStockObject(WHITE_BRUSH);
				}
				return FALSE;

			case WM_NOTIFY:
				switch(((LPNMHDR)lParam)->idFrom) {
					case EDIT_ANNIVERSARY_DATE:
						switch(((LPNMHDR)lParam)->code) {
							/*
							 * handle changed selection
							 */
							case LVN_ITEMCHANGED:
							{
								CItemData* pid;
								HWND hCheck;

								pDlg->_curSel = ((LPNMLISTVIEW)lParam)->iItem;
								pid = pDlg->ItemData(pDlg->_curSel);
								if(pid && pDlg->_bRemindEnable && (hCheck = GetDlgItem(hDlg, CHECK_REMIND))) {
									SetDlgItemInt(hDlg, EDIT_REMIND, pid->_wDaysBefore, FALSE);
									Button_SetCheck(hCheck, pid->_wReminderState);
									DlgProc(hDlg, WM_COMMAND, MAKEWPARAM(CHECK_REMIND, BN_CLICKED), (LPARAM)hCheck);
								}
								break;
							}
							/*
							 * resort the list
							 */
							case LVN_COLUMNCLICK:
							{
								LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;

								if(pDlg->_sortHeader == pnmv->iSubItem) {
									pDlg->_sortOrder *= -1;
								}
								else {
									pDlg->_sortOrder = 1;
									pDlg->_sortHeader = pnmv->iSubItem;
								}
								ListView_SortItemsEx(pDlg->_hList, (CMPPROC)cmpProc, pDlg);
								break;
							}
							/*
							 * show contact menu
							 */
							case NM_RCLICK:
							{
								CItemData* pid = pDlg->ItemData(pDlg->_curSel);
								if(pid) {
									HMENU hPopup = (HMENU) CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM) pid->_hContact, 0);
									if(hPopup) {
										POINT pt;
										GetCursorPos(&pt);
										TrackPopupMenu(hPopup, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hDlg, NULL);
										DestroyMenu(hPopup);
									}
								}
								break;
							}
							/*
							 * handle double click on contact: show message dialog
							 */
							case NM_DBLCLK:
							{
								CItemData* pid = pDlg->ItemData(((LPNMITEMACTIVATE)lParam)->iItem);
								if(pid)	CallService(MS_MSG_SENDMESSAGE,(WPARAM)pid->_hContact, NULL);
								break;
							}
						}
						break;
				}
				break;

			case WM_COMMAND:
				if(PtrIsValid(pDlg)) {
					CItemData* pid = pDlg->ItemData(pDlg->_curSel);
					
					// process contact menu command
					if(pid && CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam), MPCF_CONTACTMENU), (LPARAM)pid->_hContact))
						break;
					
					switch(LOWORD(wParam)) {
						/*
						 * enable/disable reminder checkbox is clicked
						 */
						case CHECK_REMIND: 
							if(pDlg->_bRemindEnable && HIWORD(wParam) == BN_CLICKED) {
								BOOLEAN checkState = Button_GetCheck((HWND)lParam);
								EnableWindow(GetDlgItem(hDlg, EDIT_REMIND), checkState == BST_CHECKED);
								EnableWindow(GetDlgItem(hDlg, SPIN_REMIND), checkState == BST_CHECKED);
								EnableWindow(GetDlgItem(hDlg, TXT_REMIND5), checkState == BST_CHECKED);
								if(pid && pid->_wReminderState != checkState) {
									pid->_wReminderState = checkState;
								}
							}
							break;
						/*
						 * number of days to remind in advance is edited
						 */
						case EDIT_REMIND:
							if(pid && pDlg->_bRemindEnable && HIWORD(wParam) == EN_CHANGE) {
								WORD wDaysBefore = GetDlgItemInt(hDlg, LOWORD(wParam), NULL, FALSE);
								if(pid->_wReminderState == BST_CHECKED && pid->_wDaysBefore != wDaysBefore) {
									pid->_wDaysBefore = wDaysBefore;
								}
							}
							break;
						/*
						 * the filter to display only contacts which have an anniversary in a certain 
						 * period of time is enabled/disabled
						 */
						case CHECK_DAYS: 
							if(HIWORD(wParam) == BN_CLICKED) {
								BOOLEAN isChecked = Button_GetCheck((HWND)lParam);
								EnableWindow(GetDlgItem(hDlg, EDIT_DAYS), isChecked);
								EnableWindow(GetDlgItem(hDlg, TXT_DAYS), isChecked);
								pDlg->_filter.wDaysBefore = isChecked ? GetDlgItemInt(hDlg, EDIT_DAYS, NULL, FALSE) : (WORD)-1;
								pDlg->RebuildList();
							}
							break;
						/*
						 * the number of days a contact must have an anniversary in advance to be displayed is edited
						 */
						case EDIT_DAYS:
							if(HIWORD(wParam) == EN_CHANGE) {
								WORD wNewDays = GetDlgItemInt(hDlg, LOWORD(wParam), NULL, FALSE);
								if(wNewDays != pDlg->_filter.wDaysBefore) {
									pDlg->_filter.wDaysBefore = wNewDays;
									pDlg->RebuildList();
								}
							}
							break;
						/*
						 * the filter selection of the filter combobox has changed
						 */
						case COMBO_VIEW:
							if(HIWORD(wParam) == CBN_SELCHANGE) {
								pDlg->_filter.bFilterIndex = ComboBox_GetCurSel((HWND)lParam);
								pDlg->RebuildList();
							}
							break;
					}
				}
				break;

			case WM_DRAWITEM:
	            return CallService(MS_CLIST_MENUDRAWITEM, wParam, lParam);
	        case WM_MEASUREITEM:
                return CallService(MS_CLIST_MENUMEASUREITEM, wParam, lParam);
			
			case WM_WINDOWPOSCHANGING:
				if(PtrIsValid(pDlg)) {
					CAnchor anchor((WINDOWPOS*)lParam, pDlg->_sizeMin);
					INT anchorPos;

					anchor.MoveCtrl(ICO_DLGLOGO, CAnchor::ANCHOR_RIGHT | CAnchor::ANCHOR_TOP);
					anchorPos = CAnchor::ANCHOR_LEFT | CAnchor::ANCHOR_RIGHT | CAnchor::ANCHOR_TOP;
					anchor.MoveCtrl(STATIC_LINE1, anchorPos);
					anchor.MoveCtrl(STATIC_WHITERECT, anchorPos);
					anchor.MoveCtrl(GROUP_STATS, anchorPos);

					// birthday list
					anchor.MoveCtrl(EDIT_ANNIVERSARY_DATE, CAnchor::ANCHOR_ALL);

					anchorPos = CAnchor::ANCHOR_RIGHT | CAnchor::ANCHOR_BOTTOM;
					// filter group
					anchor.MoveCtrl(GROUP_FILTER, anchorPos);
					anchor.MoveCtrl(COMBO_VIEW, anchorPos);
					anchor.MoveCtrl(CHECK_DAYS, anchorPos);
					anchor.MoveCtrl(EDIT_DAYS, anchorPos);
					anchor.MoveCtrl(TXT_DAYS, anchorPos);

					// filter group
					anchor.MoveCtrl(GROUP_REMINDER, anchorPos);
					anchor.MoveCtrl(CHECK_REMIND, anchorPos);
					anchor.MoveCtrl(EDIT_REMIND, anchorPos);
					anchor.MoveCtrl(SPIN_REMIND, anchorPos);
					anchor.MoveCtrl(TXT_REMIND6, anchorPos);
					anchor.MoveCtrl(CHECK_POPUP, anchorPos);
				}
				return FALSE;

			case WM_CLOSE:
				delete pDlg;
				break;
		}
		return FALSE;
	}

	/**
	 * name:	AddColumn
	 * class:	CAnnivList
	 * desc:	add a column to the listview
	 * param:	iSubItem	 - desired column index
	 *			pszText		 - the header text
	 *			defaultWidth - the default witdth
	 * return:	0 if successful, 1 if failed
	 **/
	BOOLEAN AddColumn(INT iSubItem, LPCTSTR pszText, INT defaultWidth) {
		LVCOLUMN lvc;
		CHAR szSetting[MAXSETTING];
		mir_snprintfA(szSetting, SIZEOF(szSetting), "AnnivDlg_Col%d", iSubItem);
		lvc.cx = DBGetSettingWord(NULL, MODNAME, szSetting, defaultWidth);
		lvc.mask = LVCF_WIDTH|LVCF_TEXT;
		lvc.iSubItem = iSubItem;
		lvc.pszText = TranslateTS(pszText);
		return ListView_InsertColumn(_hList, lvc.iSubItem++, &lvc) == -1;
	}

	/**
	 * name:	AddSubItem
	 * class:	CAnnivList
	 * desc:	set the subitem text for the current contact
	 * param:	iItem	 - index of the current row
	 *			iSubItem - column to set text for
	 *			pszText	 - text to insert
	 * return:	TRUE if successful, FALSE if failed
	 **/
	BOOLEAN AddSubItem(INT iItem, INT iSubItem, LPTSTR pszText) {
		LVITEM lvi;
		if(iSubItem > 0) {
			lvi.iItem = iItem;
			lvi.iSubItem = iSubItem;
			lvi.pszText = pszText;
			lvi.mask = LVIF_TEXT;
			return ListView_SetItem(_hList, &lvi);
		}
		return FALSE;
	}

	/**
	 * name:	AddItem
	 * class:	CAnnivList
	 * desc:	add a line to the listview
	 * param:	pszText	- text to insert
	 *			lParam	- pointer to the rows data
	 * return:	TRUE if successful, FALSE if failed
	 **/	
	BOOLEAN AddItem(LPTSTR pszText, LPARAM lParam) {
		LVITEM lvi;
		if(!pszText) return FALSE;
		lvi.iItem = 0;
		lvi.iSubItem = 0;
		lvi.pszText = pszText;
		lvi.mask = LVIF_TEXT|TVIF_PARAM;
		lvi.lParam = lParam;
		return ListView_InsertItem(_hList, &lvi);
	}

	/**
	 * name:	AddRow
	 * class:	CAnnivList
	 * desc:	add a line to the listview
	 * param:	pszText	- text to insert
	 *			hContact	- contact to add the line for
	 *			pszProto	- contact's protocol
	 *			ad			- anniversary to add
	 * return:	TRUE if successful, FALSE if failed
	 **/
	BOOLEAN AddRow(HANDLE hContact, LPCSTR pszProto, MAnnivDate &ad, MTime &mtNow, WORD wDaysBefore) {
		TCHAR szText[MAX_PATH];
		INT diff, iItem = -1;
		CItemData *pdata;
	
		//
		// first column: ETA
		//
		diff = ad.CompareDays(mtNow);
		if(diff < 0) diff += IsLeap(mtNow.Year() + 1) ? 366 : 365;
		// is filtered
		if(diff > _filter.wDaysBefore) 
			return TRUE;
		// read reminder options for the contact
		ad.DBGetReminderOpts(hContact);
		if((_filter.bFilterIndex == FILTER_DISABLED_REMINDER) && (ad.RemindOption() != BST_UNCHECKED))
			return TRUE;
		// set default offset if required
		if(ad.RemindOffset() == (WORD)-1)
			ad.RemindOffset(wDaysBefore);
		// create data object
		pdata = new CItemData(hContact, ad);
		if(!pdata) return FALSE;
		// add item
		iItem = AddItem(_itot(diff, szText, 10), (LPARAM)pdata);
		if(iItem == -1) {
			delete pdata;
			return FALSE;
		}

		// second column: contact name
		AddSubItem(iItem, COLUMN_CONTACT, DBGetContactDisplayName(hContact));

		// third column: protocol
#ifdef _UNICODE
		MultiByteToWideChar(myGlobals.CodePage, 0, pszProto, -1, szText, SIZEOF(szText));
		AddSubItem(iItem, COLUMN_PROTO, szText);
#else
		AddSubItem(iItem, COLUMN_PROTO, (LPSTR)pszProto);
#endif
		// forth line: age
		AddSubItem(iItem, COLUMN_AGE, _itot(ad.Age(&mtNow), szText, 10));

		// fifth line: anniversary
		AddSubItem(iItem, COLUMN_DESC, (LPTSTR)ad.Description());

		// sixth line: date
		ad.DateFormat(szText, SIZEOF(szText));
		AddSubItem(iItem, COLUMN_DATE, szText);
		
		_numRows++;
		return TRUE;
	}

	/**
	 * name:	RebuildList
	 * class:	CAnnivList
	 * desc:	clears the list and adds contacts again, according to the current filter settings
	 * param:	none
	 * return:	nothing
	 **/
	VOID RebuildList() {
		HANDLE hContact;
		LPCSTR pszProto;
		DBVARIANT dbv;
		MTime mtNow;
		MAnnivDate ad;
		INT		i = 0;
		DWORD   age = 0;
        WORD	wDaysBefore = DBGetSettingWord(NULL, MODNAME, SET_REMIND_OFFSET, DEFVAL_REMIND_OFFSET),
				numMale = 0,
				numFemale = 0,
				numContacts = 0,
				numBirthContacts = 0;

		ShowWindow(_hList, SW_HIDE);
		DeleteAllItems();
		mtNow.GetLocalTime();

		// insert the items into the list
		for(hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
			hContact != NULL;
			hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
		{
			numContacts++;
			// filter protocol
			pszProto = DBGetContactBaseProto(hContact);
			if(pszProto) {
				if(!DBGetSettingEx(hContact, USERINFO, pszProto, SET_CONTACT_GENDER, &dbv)) {
					switch(dbv.bVal) {
						case 'M': numMale++; break;
						case 'F': numFemale++; break;
					}
					DBFreeVariant(&dbv);
				}
				if(!ad.DBGetBirthDate(hContact, pszProto)) {
					age += ad.Age(&mtNow);
					numBirthContacts++;

					// add birthday
					if(
						(_filter.bFilterIndex != FILTER_ANNIV) &&
						(!_filter.pszProto || !strcmpi(pszProto, _filter.pszProto))
					  )
					{	
						AddRow(hContact, pszProto, ad, mtNow, wDaysBefore);
					}
				}

				// add anniversaries
				if(_filter.bFilterIndex != FILTER_BIRTHDAY && (!_filter.pszProto || !strcmpi(pszProto, _filter.pszProto))) {
					for(i = 0; !ad.DBGetAnniversaryDate(hContact, i); i++) {
						if(!_filter.pszAnniv || !_tcsicmp(_filter.pszAnniv, ad.Description()))
							AddRow(hContact, pszProto, ad, mtNow, wDaysBefore);
					}
				}
			}
		}
		ListView_SortItemsEx(_hList, (CMPPROC)cmpProc, this);
		ShowWindow(_hList, SW_SHOW);

		// display statistics
		SetDlgItemInt(_hDlg, TXT_NUMBIRTH, numBirthContacts, FALSE);
		SetDlgItemInt(_hDlg, TXT_NUMCONTACT, numContacts, FALSE);
		SetDlgItemInt(_hDlg, TXT_FEMALE, numFemale, FALSE);
		SetDlgItemInt(_hDlg, TXT_MALE, numMale, FALSE);
		SetDlgItemInt(_hDlg, TXT_AGE, numBirthContacts > 0 ? max(0, (age - (age % numBirthContacts)) / numBirthContacts) : 0, FALSE);
	}

	/**
	 * name:	DeleteAllItems
	 * class:	CAnnivList
	 * desc:	delete all items from the listview
	 * param:	none
	 * return:	nothing
	 **/	
	VOID DeleteAllItems() {
		CItemData *pid;
		
		for(INT i = 0; i < _numRows; i++) {
			pid = ItemData(i);
			if(pid) delete pid;
		}
		ListView_DeleteAllItems(_hList);
		_numRows = 0;
	}

	/**
	 * name:	ItemData
	 * class:	CAnnivList
	 * desc:	get data structure accociated with a list item
	 * param:	iItem	- index of the desired item
	 * return:	nothing
	 **/
	CItemData* ItemData(INT iItem) {
		if(_hList && iItem >= 0 && iItem < _numRows) {
			LVITEM lvi;
		
			lvi.mask = LVIF_PARAM;
			lvi.iItem = iItem;
			lvi.iSubItem = 0;
			if(ListView_GetItem(_hList, &lvi) && PtrIsValid(lvi.lParam))
				return (CItemData*)lvi.lParam;
		}
		return NULL;
	}

	/**
	 * name:	LoadFilter
	 * class:	CAnnivList
	 * desc:	load all filter settings from db
	 * param:	none
	 * return:	nothing
	 **/	
	VOID LoadFilter() {
		_filter.wDaysBefore = DBGetSettingWord(NULL, MODNAME, SET_ANNIVLIST_FILTER_DAYS, 9);
		_filter.bFilterIndex = DBGetSettingByte(NULL, MODNAME, SET_ANNIVLIST_FILTER_INDEX, 0);
	}

	/**
	 * name:	LoadFilter
	 * class:	CAnnivList
	 * desc:	save all filter settings to db
	 * param:	none
	 * return:	nothing
	 **/	
	VOID SaveFilter() {
		if(_hDlg) {
			DBWriteContactSettingWord(NULL, MODNAME, SET_ANNIVLIST_FILTER_DAYS, (WORD)GetDlgItemInt(_hDlg, EDIT_DAYS, NULL, FALSE));
			DBWriteContactSettingByte(NULL, MODNAME, SET_ANNIVLIST_FILTER_DAYSENABLED, (BYTE)Button_GetCheck(GetDlgItem(_hDlg, CHECK_DAYS)));
			DBWriteContactSettingByte(NULL, MODNAME, SET_ANNIVLIST_FILTER_INDEX, (BYTE)ComboBox_GetCurSel(GetDlgItem(_hDlg, EDIT_DAYS)));
		}
	}

	/**
	 * name:	OnShutdown
	 * class:	CAnnivList
	 * desc:	Miranda wants to shutdown
	 * param:	none
	 * return:	nothing
	 **/
	static INT OnShutdown(WPARAM wParam, LPARAM lParam) {
		delete gpDlg;
		return 0;
	}

public:

	/**
	 * name:	CAnnivList
	 * class:	CAnnivList
	 * desc:	default constructor
	 * param:	pObject	- pointer to the pointer of the object created for this class, which is to set to zero on delete
	 * return:	nothing
	 **/
	CAnnivList() {
		_hList = NULL;
		_sortHeader = 0;
		_sortOrder = 1;
		_curSel = -1;
		_numRows = 0;
		LoadFilter();
		_hDlg = CreateDialogParam(ghInst, MAKEINTRESOURCE(IDD_ANNIVERSARY_LIST), NULL, (DLGPROC)DlgProc, (LPARAM)this);
		if(_hDlg) _mHookExit = HookEvent(ME_SYSTEM_SHUTDOWN, OnShutdown);
		else delete this;
	}

	/**
	 * name:	~CAnnivList
	 * class:	CAnnivList
	 * desc:	default destructor
	 * param:	none
	 * return:	nothing
	 **/
	~CAnnivList() {
		// close window if required
		if(_hDlg) {
			// save list state
			if(_hList) {
				CHAR szSetting[MAXSETTING];
				INT i, numColumns = Header_GetItemCount(ListView_GetHeader(_hList));
				for(i = 0; i < numColumns; i++) {
					mir_snprintfA(szSetting, MAXSETTING, "AnnivDlg_Col%d", i);
					DBWriteContactSettingWord(NULL, MODNAME, szSetting, (WORD)ListView_GetColumnWidth(_hList, i));
				}
				DeleteAllItems();
			}
			// remember popup setting
			DBWriteContactSettingByte(NULL, MODNAME, SET_ANNIVLIST_POPUP, (BYTE)IsDlgButtonChecked(_hDlg, CHECK_POPUP));
			// save window position, size and column widths
			Utils_SaveWindowPosition(_hDlg, NULL, MODNAME, "AnnivDlg_");
			SaveFilter();
			SetUserData(_hDlg, NULL);
			DestroyWindow(_hDlg);
		}	
		UnhookEvent(ME_SYSTEM_SHUTDOWN);
		gpDlg = NULL;
	}

	/**
	 * name:	BringToFront
	 * class:	CAnnivList
	 * desc:	brings anniversary list to the top
	 * param:	none
	 * return:	nothing
	 **/
	VOID BringToFront()
	{
		ShowWindow(_hDlg, SW_RESTORE);
		SetForegroundWindow(_hDlg);
		SetFocus(_hDlg);
	}

}; // class CAnnivList

/***********************************************************************************************************
 * service handlers
 ***********************************************************************************************************/

/**
 * name:	ShowDialog
 * desc:	service function that is called list all anniversaries
 * param:	wParam	- flag
 *			lParam	- flag
 * return:	always 0
 **/
INT ShowDialog(WPARAM wParam, LPARAM lParam) 
{
	if(!gpDlg) {
		try {
			gpDlg = new CAnnivList();
		}
		catch(...) {
			gpDlg = NULL;
		}
	} else {
		gpDlg->BringToFront();
	}
	return 0;
}

/***********************************************************************************************************
 * loading and unloading module
 ***********************************************************************************************************/

/**
 * name:	  OnTopToolBarModuleLoaded
 * namespace: NAnniversaryList
 * desc:	  add the anniversary list button to toptoolbar
 * param:	  none
 * return:	  nothing
 **/
VOID OnTopToolBarModuleLoaded()
{
	TTBButton ttb;
	HBITMAP hBmp = LoadBitmap(ghInst, MAKEINTRESOURCE(IDB_LIST_ANNIV));

	ZeroMemory(&ttb, sizeof(TTBButton));
	ttb.cbSize = sizeof(TTBButton);
	ttb.dwFlags = TTBBF_VISIBLE | TTBBF_SHOWTOOLTIP;
	ttb.pszServiceDown = MS_USERINFO_REMINDER_LIST;
	ttb.hbBitmapUp = ttb.hbBitmapDown = hBmp;
	ttb.name = Translate("Anniversary list");
	CallService(MS_TTB_ADDBUTTON, (WPARAM) &ttb, 0);
}

/**
 * name:	  LoadModule
 * namespace: NAnniversaryList
 * desc:	  load the anniversary list module
 * param:	  none
 * return:	  nothing
 **/
VOID LoadModule()
{
	CreateServiceFunction(MS_USERINFO_REMINDER_LIST, ShowDialog);
}

} // namespace NAnniversaryList